#region Copyright
/// <copyright>
/// Copyright (c) 2007 Ilia Shramko.
/// Released under the terms of the GNU General Public License.
/// </copyright>
#endregion

using System;
using System.Collections.Generic;
using System.Text;

namespace Osh.Teletext
{
	public class TeletextPage
	{
		public const int MinRowNumber = 0;
		public const int MaxRowNumber = 25;
		public const int RowLength = 40;

		private TeletextMagazine _magazine;
		private DateTime _lastPacketTimeUtc;
		private TimeSpan _presentationTime;
		private PageHeaderPacket _header;
		private NormalPacket[] _rows;
		private List<NonDisplayablePacket> _nonDisplayablePackets;

		public TeletextPage(TeletextMagazine magazine, PageHeaderPacket header) : this(magazine, header, TimeSpan.Zero)
		{
		}

		public TeletextPage(TeletextMagazine magazine, PageHeaderPacket header, TimeSpan presentationTime)
		{
			if(magazine == null)
				throw new ArgumentNullException("magazine");
			if(header == null)
				throw new ArgumentNullException("header");
			
			_magazine = magazine;
			_header = header;
			_rows = new NormalPacket[NormalPacket.MaxNormalPacketNumber + 1];
			_presentationTime = presentationTime;
			_lastPacketTimeUtc = DateTime.UtcNow;
		}

		public void AddPacket(NormalPacket packet)
		{
			_rows[packet.Number] = packet;
			_lastPacketTimeUtc = DateTime.UtcNow;
		}

		public void AddPacket(NonDisplayablePacket packet)
		{
			if(_nonDisplayablePackets == null)
				_nonDisplayablePackets = new List<NonDisplayablePacket>();
			_nonDisplayablePackets.Add(packet);
			_lastPacketTimeUtc = DateTime.UtcNow;
		}

		/// <summary>
		/// Reuses the page by setting a new header, presentation time and erasing the content if necessary
		/// (a new header's ErasePage property equals true).
		/// </summary>
		/// <param name="newHeader">A new page header.</param>
		/// <param name="newPresentationTime">A new page presentaion time.</param>
		public void Reuse(PageHeaderPacket newHeader, TimeSpan newPresentationTime)
		{
			if(newHeader.ErasePage)
				Erase();

			_header = newHeader;
			_presentationTime = newPresentationTime;
			_lastPacketTimeUtc = DateTime.UtcNow;
		}

		/// <summary>
		/// Erases rows X/1 to X/28.
		/// </summary>
		public void Erase()
		{
			Array.Clear(_rows, 0, _rows.Length);
			if(_nonDisplayablePackets != null)
				_nonDisplayablePackets.Clear();
		}

		public DateTime LastPacketTimeUtc
		{
			get { return _lastPacketTimeUtc; }
		}

		public PageAddress Address
		{
			get { return _header.PageAddress; }
		}

		public TeletextMagazine Magazine
		{
			get { return _magazine; }
		}

		public TimeSpan PresentationTime
		{
			get { return _presentationTime; }
			set { _presentationTime = value; }
		}

		public PageHeaderPacket Header
		{
			get { return _header; }
		}

		public NormalPacket[] Rows
		{
			get { return _rows; }
		}

		public byte GetCharCode(int row, int col)
		{
			if(row < 0 || row > 25)
				throw new ArgumentOutOfRangeException("row");
			if(col < 0 || col >= 40)
				throw new ArgumentOutOfRangeException("col");

			if(row == 0)
			{
				if(col < 8)
					return 0x20;
				return (byte)(_header.Data[col - 8] & 0x7F);
			}
			else
			{
				if(_rows[row] == null)
					return 0xFF;
				return (byte)(_rows[row].Data[col] & 0x7F);
			}
		}

		public byte[] GetRowData(int row)
		{
			if(row < MinRowNumber || row > MaxRowNumber)
				throw new ArgumentOutOfRangeException("row");

			if(row == 0)
				return _header.Data;
			else
				return (_rows[row] == null ? null : _rows[row].Data);
		}

		public string GetPageText()
		{
			return TeletextDecoder.DecodeText(this);
		}

		public string GetRowText(int row)
		{
			return TeletextDecoder.DecodeText(this, row);
		}

		public string GetPageHtml()
		{
			return TeletextDecoder.DecodeTextToHtml(this);
		}

		public string GetRowHtml(int row)
		{
			return TeletextDecoder.DecodeTextToHtml(this, row);
		}

		public CharReplacement GetCharReplacement(CharPosition position)
		{
			if(_nonDisplayablePackets == null)
				return CharReplacement.Empty;

			CharReplacement result = CharReplacement.Empty;

			for(int i = 0; i < _nonDisplayablePackets.Count; i++)
			{
				PacketX26 packet = _nonDisplayablePackets[i] as PacketX26;
				if(packet == null)
					continue;

				List<CharReplacement> replacements = packet.CharReplacements;
				if(replacements == null || replacements.Count == 0)
					continue;

				for(int r = 0; r < replacements.Count; r++)
					if(replacements[r].Position == position)
						result = replacements[r];
			}
			
			return result;
		}

		public bool TryGetLink(int linkIndex, out PageAddress link)
		{
			if(linkIndex < 0 || linkIndex >= PacketX270.MaxNumberOfLinks)
				throw new ArgumentOutOfRangeException("linkIndex");

			if(_nonDisplayablePackets != null)
			{
				foreach(NonDisplayablePacket packet in _nonDisplayablePackets)
				{
					PacketX270 linkPacket = packet as PacketX270;
					if(linkPacket == null)
						continue;

					link = linkPacket.Links[linkIndex];
					return true;
				}
			}
			link = PageAddress.Null;
			return false;
		}

		public Charset DefaultCharset
		{
			get
			{
				Charset charset = Charset.Empty;
				if(_nonDisplayablePackets != null)
				{
					int numberOfPackets = _nonDisplayablePackets.Count;
					for(int i = 0; i < numberOfPackets; i++)
					{
						PacketX280Format1 packet = _nonDisplayablePackets[i] as PacketX280Format1;
						if(packet != null && packet.PageFunction == PageFunction.BasicLevel1Teletext)
						{
							charset = packet.BasicLevel1TeletextCoding.DefaultCharset;
							if(charset != Charset.Empty)
								break; // X/28/0 takes precedence over X/28/4, so break the loop
						}

						PacketX284 packet4 = _nonDisplayablePackets[i] as PacketX284;
						if(packet4 != null && packet4.PageFunction == PageFunction.BasicLevel1Teletext)
						{
							charset = packet4.BasicLevel1TeletextCoding.DefaultCharset;
							// X/28/0 takes precedence over X/28/4, so continue the loop
						}
					}
				}
				if(charset == Charset.Empty)
					charset = new Charset(_magazine.Service.DefaultCharset, _header.NationalCharacterSubset);

				return charset;
			}
		}

		public Charset SecondCharset
		{
			get
			{
				Charset charset = Charset.Empty;
				if(_nonDisplayablePackets == null)
					return charset;

				int numberOfPackets = _nonDisplayablePackets.Count;
				for(int i = 0; i < numberOfPackets; i++)
				{
					PacketX280Format1 packet = _nonDisplayablePackets[i] as PacketX280Format1;
					if(packet != null && packet.PageFunction == PageFunction.BasicLevel1Teletext)
					{
						charset = packet.BasicLevel1TeletextCoding.SecondCharset;
						if(charset != Charset.Empty)
							break; // X/28/0 takes precedence over X/28/4, so break the loop
					}

					PacketX284 packet4 = _nonDisplayablePackets[i] as PacketX284;
					if(packet4 != null && packet4.PageFunction == PageFunction.BasicLevel1Teletext)
					{
						charset = packet4.BasicLevel1TeletextCoding.SecondCharset;
						// X/28/0 takes precedence over X/28/4, so continue the loop
					}
				}
				return charset;
			}
		}

	}
}
